;
(function() {

    "use strict";

    var objectTypes = {
        'function': true,
        'object': true
    };

    function checkGlobal(value) {
        return (value && value.Object === Object) ? value : null;
    }

    /** Built-in method references without a dependency on `root`. */
    var freeParseFloat = parseFloat,
        freeParseInt = parseInt;

    /** Detect free variable `exports`. */
    var freeExports = (objectTypes[typeof exports] && exports && !exports.nodeType) ? exports : undefined;

    /** Detect free variable `module`. */
    var freeModule = (objectTypes[typeof module] && module && !module.nodeType) ? module : undefined;

    /** Detect the popular CommonJS extension `module.exports`. */
    var moduleExports = (freeModule && freeModule.exports === freeExports) ? freeExports : undefined;

    /** Detect free variable `global` from Node.js. */
    var freeGlobal = checkGlobal(freeExports && freeModule && typeof global == 'object' && global);

    /** Detect free variable `self`. */
    var freeSelf = checkGlobal(objectTypes[typeof self] && self);

    /** Detect free variable `window`. */
    var freeWindow = checkGlobal(objectTypes[typeof window] && window);

    /** Detect `this` as the global object. */
    var thisGlobal = checkGlobal(objectTypes[typeof this] && this);

    /**
     * Used as a reference to the global object.
     *
     * The `this` value is used if it's the global object to avoid Greasemonkey's
     * restricted `window` object, otherwise the `window` object is used.
     */
    var root = freeGlobal ||
        ((freeWindow !== (thisGlobal && thisGlobal.window)) && freeWindow) ||
        freeSelf || thisGlobal || Function('return this')();

    if (!('gc' in window)) {
        window.gc = function() {}
    }

    if (!HTMLCanvasElement.prototype.toBlob) {
        Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
            value: function(callback, type, quality) {

                var binStr = atob(this.toDataURL(type, quality).split(',')[1]),
                    len = binStr.length,
                    arr = new Uint8Array(len);

                for (var i = 0; i < len; i++) {
                    arr[i] = binStr.charCodeAt(i);
                }

                callback(new Blob([arr], {
                    type: type || 'image/png'
                }));
            }
        });
    }

    // @license http://opensource.org/licenses/MIT
    // copyright Paul Irish 2015


    // Date.now() is supported everywhere except IE8. For IE8 we use the Date.now polyfill
    //   github.com/Financial-Times/polyfill-service/blob/master/polyfills/Date.now/polyfill.js
    // as Safari 6 doesn't have support for NavigationTiming, we use a Date.now() timestamp for relative values

    // if you want values similar to what you'd get with real perf.now, place this towards the head of the page
    // but in reality, you're just getting the delta between now() calls, so it's not terribly important where it's placed


    (function() {

        if ("performance" in window == false) {
            window.performance = {};
        }

        Date.now = (Date.now || function() { // thanks IE8
            return new Date().getTime();
        });

        if ("now" in window.performance == false) {

            var nowOffset = Date.now();

            if (performance.timing && performance.timing.navigationStart) {
                nowOffset = performance.timing.navigationStart
            }

            window.performance.now = function now() {
                return Date.now() - nowOffset;
            }
        }

    })();


    function pad(n) {
        return String("0000000" + n).slice(-7);
    }
    // https://developer.mozilla.org/en-US/Add-ons/Code_snippets/Timers

    var g_startTime = window.Date.now();

    function guid() {
        function s4() {
            return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
        }
        return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
    }

    function CCFrameEncoder(settings) {

        var _handlers = {};

        this.settings = settings;

        this.on = function(event, handler) {

            _handlers[event] = handler;

        };

        this.emit = function(event) {

            var handler = _handlers[event];
            if (handler) {

                handler.apply(null, Array.prototype.slice.call(arguments, 1));

            }

        };

        this.filename = settings.name || guid();
        this.extension = '';
        this.mimeType = '';

    }

    CCFrameEncoder.prototype.start = function() {};
    CCFrameEncoder.prototype.stop = function() {};
    CCFrameEncoder.prototype.add = function() {};
    CCFrameEncoder.prototype.save = function() {};
    CCFrameEncoder.prototype.dispose = function() {};
    CCFrameEncoder.prototype.safeToProceed = function() {
        return true;
    };
    CCFrameEncoder.prototype.step = function() {
        console.log('Step not set!')
    }

    function CCTarEncoder(settings) {

        CCFrameEncoder.call(this, settings);

        this.extension = '.tar'
        this.mimeType = 'application/x-tar'
        this.fileExtension = '';

        this.tape = null

        this.baseFilename = 'j360';
        this.count = 0;
    }

    var globalChunkCount = 0;
    var globalPartCount = 0;

    CCTarEncoder.prototype = Object.create(CCFrameEncoder.prototype);

    CCTarEncoder.prototype.start = function() {

        this.dispose();

    };

    CCTarEncoder.prototype.add = function(blob) {

        var fileReader = new FileReader();
        fileReader.onload = function() {
            this.tape.append(pad(globalChunkCount) + this.fileExtension, new Uint8Array(fileReader.result));
            if (this.settings.autoSaveTime > 0 && (this.count / this.settings.framerate) >= this.settings.autoSaveTime) {
                this.save(function(blob) {
                    this.filename = this.baseFilename + '_part_' + pad(globalPartCount);
                    download(blob, this.filename + this.extension, this.mimeType);
                    globalPartCount++;
                    globalChunkCount++;
                    this.dispose();
                    this.step();
                }.bind(this))
            } else {
                globalChunkCount++;
                this.count++;
                this.step();
            }
        }.bind(this);
        fileReader.readAsArrayBuffer(blob);
    }

    CCTarEncoder.prototype.save = function(callback) {

        callback(this.tape.save());

    }

    CCTarEncoder.prototype.dispose = function() {

        this.tape = new Tar();
        this.count = 0;

    }



    function CCJPEGEncoder(settings) {

        CCTarEncoder.call(this, settings);

        this.type = 'image/jpeg';
        this.fileExtension = '.jpg';
        this.quality = (settings.quality / 100) || .8;

    }

    CCJPEGEncoder.prototype = Object.create(CCTarEncoder.prototype);

    CCJPEGEncoder.prototype.add = function(canvas) {

        canvas.toBlob(function(blob) {
            CCTarEncoder.prototype.add.call(this, blob);
        }.bind(this), this.type, this.quality)

    }

    function CC360Encoder(settings) {

        CCTarEncoder.call(this, settings);

        // this.type = 'image/png';
        // this.fileExtension = '.png';


        this.type = 'image/jpeg';
        this.fileExtension = '.jpg';
        this.quality = (settings.quality / 100) || 1;



    }

    CC360Encoder.prototype = Object.create(CCTarEncoder.prototype);

    CC360Encoder.prototype.add = function(canvas) {
        equiManaged.preBlob(equiManaged.cubeCamera);
        equiManaged.canvas.toBlob(function(blob) {
            CCTarEncoder.prototype.add.call(this, blob);
        }.bind(this), this.type, this.quality)

    }

    function CCPNGEncoder(settings) {

        CCTarEncoder.call(this, settings);

        this.type = 'image/png';
        this.fileExtension = '.png';

    }

    CCPNGEncoder.prototype = Object.create(CCTarEncoder.prototype);

    CCPNGEncoder.prototype.add = function(canvas) {

        canvas.toBlob(function(blob) {
            CCTarEncoder.prototype.add.call(this, blob);
        }.bind(this), this.type)

    }

    /*

        WebM Encoder

    */

    function CCWebMEncoder(settings) {

        var canvas = document.createElement('canvas');
        if (canvas.toDataURL('image/webp').substr(5, 10) !== 'image/webp') {
            console.log("WebP not supported - try another export format")
        }

        CCFrameEncoder.call(this, settings);

        settings.quality = (settings.quality / 100) || .8;

        this.extension = '.webm'
        this.mimeType = 'video/webm'
        this.baseFilename = this.filename;

        this.frames = [];
        this.part = 1;

    }

    CCWebMEncoder.prototype = Object.create(CCFrameEncoder.prototype);

    CCWebMEncoder.prototype.start = function(canvas) {

        this.dispose();

    }

    CCWebMEncoder.prototype.add = function(canvas) {

        this.frames.push(canvas.toDataURL('image/webp', this.quality));

        if (this.settings.autoSaveTime > 0 && (this.frames.length / this.settings.framerate) >= this.settings.autoSaveTime) {
            this.save(function(blob) {
                this.filename = this.baseFilename + '-part-' + pad(this.part);
                download(blob, this.filename + this.extension, this.mimeType);
                this.dispose();
                this.part++;
                this.filename = this.baseFilename + '-part-' + pad(this.part);
                this.step();
            }.bind(this))
        } else {
            this.step();
        }

    }

    CCWebMEncoder.prototype.save = function(callback) {

        if (!this.frames.length) return;

        var webm = Whammy.fromImageArray(this.frames, this.settings.framerate)
        var blob = new Blob([webm], {
            type: "octet/stream"
        });
        callback(blob);

    }

    CCWebMEncoder.prototype.dispose = function(canvas) {

        this.frames = [];

    }

    function CCFFMpegServerEncoder(settings) {

        CCFrameEncoder.call(this, settings);

        settings.quality = (settings.quality / 100) || .8;

        this.encoder = new FFMpegServer.Video(settings);
        this.encoder.on('process', function() {
            this.emit('process')
        }.bind(this));
        this.encoder.on('finished', function(url, size) {
            var cb = this.callback;
            if (cb) {
                this.callback = undefined;
                cb(url, size);
            }
        }.bind(this));
        this.encoder.on('progress', function(progress) {
            if (this.settings.onProgress) {
                this.settings.onProgress(progress)
            }
        }.bind(this));
        this.encoder.on('error', function(data) {
            alert(JSON.stringify(data, null, 2));
        }.bind(this));

    }

    CCFFMpegServerEncoder.prototype = Object.create(CCFrameEncoder.prototype);

    CCFFMpegServerEncoder.prototype.start = function() {

        this.encoder.start(this.settings);

    };

    CCFFMpegServerEncoder.prototype.add = function(canvas) {

        this.encoder.add(canvas);

    }

    CCFFMpegServerEncoder.prototype.save = function(callback) {

        this.callback = callback;
        this.encoder.end();

    }

    CCFFMpegServerEncoder.prototype.safeToProceed = function() {
        return this.encoder.safeToProceed();
    };

    /*
        HTMLCanvasElement.captureStream()
    */

    function CCStreamEncoder(settings) {

        CCFrameEncoder.call(this, settings);

        this.framerate = this.settings.framerate;
        this.type = 'video/webm';
        this.extension = '.webm';
        this.stream = null;
        this.mediaRecorder = null;
        this.chunks = [];

    }

    CCStreamEncoder.prototype = Object.create(CCFrameEncoder.prototype);

    CCStreamEncoder.prototype.add = function(canvas) {

        if (!this.stream) {
            this.stream = canvas.captureStream(this.framerate);
            this.mediaRecorder = new MediaRecorder(this.stream);
            this.mediaRecorder.start();

            this.mediaRecorder.ondataavailable = function(e) {
                this.chunks.push(e.data);
            }.bind(this);

        }
        this.step();

    }

    CCStreamEncoder.prototype.save = function(callback) {

        this.mediaRecorder.onstop = function(e) {
            var blob = new Blob(this.chunks, {
                'type': 'video/webm'
            });
            this.chunks = [];
            callback(blob);

        }.bind(this);

        this.mediaRecorder.stop();

    }

    /*function CCGIFEncoder( settings ) {

        CCFrameEncoder.call( this );

        settings.quality = settings.quality || 6;
        this.settings = settings;

        this.encoder = new GIFEncoder();
        this.encoder.setRepeat( 1 );
        this.encoder.setDelay( settings.step );
        this.encoder.setQuality( 6 );
        this.encoder.setTransparent( null );
        this.encoder.setSize( 150, 150 );

        this.canvas = document.createElement( 'canvas' );
        this.ctx = this.canvas.getContext( '2d' );
        
    }

    CCGIFEncoder.prototype = Object.create( CCFrameEncoder );

    CCGIFEncoder.prototype.start = function() {

        this.encoder.start();

    }

    CCGIFEncoder.prototype.add = function( canvas ) {

        this.canvas.width = canvas.width;
        this.canvas.height = canvas.height;
        this.ctx.drawImage( canvas, 0, 0 );
        this.encoder.addFrame( this.ctx );

        this.encoder.setSize( canvas.width, canvas.height );
        var readBuffer = new Uint8Array(canvas.width * canvas.height * 4);
        var context = canvas.getContext( 'webgl' );
        context.readPixels(0, 0, canvas.width, canvas.height, context.RGBA, context.UNSIGNED_BYTE, readBuffer);
        this.encoder.addFrame( readBuffer, true );

    }

    CCGIFEncoder.prototype.stop = function() {

        this.encoder.finish();
        
    }

    CCGIFEncoder.prototype.save = function( callback ) {

        var binary_gif = this.encoder.stream().getData();

        var data_url = 'data:image/gif;base64,'+encode64(binary_gif);
        window.location = data_url;
        return;

        var blob = new Blob( [ binary_gif ], { type: "octet/stream" } );
        var url = window.URL.createObjectURL( blob );
        callback( url );

    }*/

    function CCGIFEncoder(settings) {

        CCFrameEncoder.call(this, settings);

        settings.quality = 31 - ((settings.quality * 30 / 100) || 10);
        settings.workers = settings.workers || 8;

        this.extension = '.gif'
        this.mimeType = 'image/gif'

        this.canvas = document.createElement('canvas');
        this.ctx = this.canvas.getContext('2d');
        this.sizeSet = false;

        this.encoder = new GIF({
            workers: settings.workers,
            quality: settings.quality,
            workerScript: settings.workersPath + 'gif.worker.js'
        });

        this.encoder.on('progress', function(progress) {
            if (this.settings.onProgress) {
                this.settings.onProgress(progress)
            }
        }.bind(this));

        this.encoder.on('finished', function(blob) {
            var cb = this.callback;
            if (cb) {
                this.callback = undefined;
                cb(blob);
            }
        }.bind(this));

    }

    CCGIFEncoder.prototype = Object.create(CCFrameEncoder.prototype);

    CCGIFEncoder.prototype.add = function(canvas) {

        if (!this.sizeSet) {
            this.encoder.setOption('width', canvas.width);
            this.encoder.setOption('height', canvas.height);
            this.sizeSet = true;
        }

        this.canvas.width = canvas.width;
        this.canvas.height = canvas.height;
        this.ctx.drawImage(canvas, 0, 0);

        this.encoder.addFrame(this.ctx, {
            copy: true,
            delay: this.settings.step
        });
        this.step();

        /*this.encoder.setSize( canvas.width, canvas.height );
        var readBuffer = new Uint8Array(canvas.width * canvas.height * 4);
        var context = canvas.getContext( 'webgl' );
        context.readPixels(0, 0, canvas.width, canvas.height, context.RGBA, context.UNSIGNED_BYTE, readBuffer);
        this.encoder.addFrame( readBuffer, true );*/

    }

    CCGIFEncoder.prototype.save = function(callback) {

        this.callback = callback;

        this.encoder.render();

    }

    function CCapture(settings) {

        var _settings = settings || {},
            _date = new Date(),
            _verbose,
            _display,
            _time,
            _startTime,
            _performanceTime,
            _performanceStartTime,
            _step,
            _encoder,
            _timeouts = [],
            _intervals = [],
            _frameCount = 0,
            _intermediateFrameCount = 0,
            _lastFrame = null,
            _requestAnimationFrameCallbacks = [],
            _capturing = false,
            _handlers = {};

        _settings.framerate = _settings.framerate || 25;
        _settings.motionBlurFrames = 2 * (_settings.motionBlurFrames || 1);
        _verbose = _settings.verbose || false;
        _display = _settings.display || true;
        _settings.step = 1000.0 / _settings.framerate;
        _settings.timeLimit = _settings.timeLimit || 0;
        _settings.frameLimit = _settings.frameLimit || 0;
        _settings.startTime = _settings.startTime || 0;

        var _timeDisplay = document.createElement('div');
        _timeDisplay.style.position = 'absolute';
        _timeDisplay.style.left = _timeDisplay.style.top = 0
        _timeDisplay.style.backgroundColor = 'black';
        _timeDisplay.style.fontFamily = 'monospace'
        _timeDisplay.style.fontSize = '16px'
        _timeDisplay.style.padding = '5px'
        _timeDisplay.style.color = 'red';
        _timeDisplay.style.zIndex = 100000
        if (_settings.display) document.body.appendChild(_timeDisplay);

        var canvasMotionBlur = document.createElement('canvas');
        var ctxMotionBlur = canvasMotionBlur.getContext('2d');
        var bufferMotionBlur;
        var imageData;

        _log('Step is set to ' + _settings.step + 'ms');

        var _encoders = {
            gif: CCGIFEncoder,
            webm: CCWebMEncoder,
            ffmpegserver: CCFFMpegServerEncoder,
            png: CCPNGEncoder,
            threesixty: CC360Encoder,
            jpg: CCJPEGEncoder,
            'webm-mediarecorder': CCStreamEncoder
        };

        var ctor = _encoders[_settings.format];
        if (!ctor) {
            throw "Error: Incorrect or missing format: Valid formats are " + Object.keys(_encoders).join(", ");
        }
        _encoder = new ctor(_settings);
        _encoder.step = _step

        _encoder.on('process', _process);
        _encoder.on('progress', _progress);

        if ("performance" in window == false) {
            window.performance = {};
        }

        Date.now = (Date.now || function() { // thanks IE8
            return new Date().getTime();
        });

        if ("now" in window.performance == false) {

            var nowOffset = Date.now();

            if (performance.timing && performance.timing.navigationStart) {
                nowOffset = performance.timing.navigationStart
            }

            window.performance.now = function now() {
                return Date.now() - nowOffset;
            }
        }

        var _oldSetTimeout = window.setTimeout,
            _oldSetInterval = window.setInterval,
            _oldClearTimeout = window.clearTimeout,
            _oldRequestAnimationFrame = window.requestAnimationFrame,
            _oldNow = window.Date.now,
            _oldPerformanceNow = window.performance.now,
            _oldGetTime = window.Date.prototype.getTime;
        // Date.prototype._oldGetTime = Date.prototype.getTime;

        var media = [];

        function _init() {

            _log('Capturer start');

            _startTime = window.Date.now();
            _time = _startTime + _settings.startTime;
            _performanceStartTime = window.performance.now();
            _performanceTime = _performanceStartTime + _settings.startTime;

            window.Date.prototype.getTime = function() {
                return _time;
            };
            window.Date.now = function() {
                return _time;
            };

            window.setTimeout = function(callback, time) {
                var t = {
                    callback: callback,
                    time: time,
                    triggerTime: _time + time
                };
                _timeouts.push(t);
                _log('Timeout set to ' + t.time);
                return t;
            };
            window.clearTimeout = function(id) {
                for (var j = 0; j < _timeouts.length; j++) {
                    if (_timeouts[j] == id) {
                        _timeouts.splice(j, 1);
                        _log('Timeout cleared');
                        continue;
                    }
                }
            };
            window.setInterval = function(callback, time) {
                var t = {
                    callback: callback,
                    time: time,
                    triggerTime: _time + time
                };
                _intervals.push(t);
                _log('Interval set to ' + t.time);
                return t;
            };
            window.clearInterval = function(id) {
                _log('clear Interval');
                return null;
            };
            window.requestAnimationFrame = function(callback) {
                _requestAnimationFrameCallbacks.push(callback);
            };
            window.performance.now = function() {
                return _performanceTime;
            };

            function hookCurrentTime() { 
                if (!this._hooked) {
                    this._hooked = true;
                    this._hookedTime = this.currentTime || 0;
                    this.pause();
                    media.push(this);
                }
                return this._hookedTime + _settings.startTime;
            };

            try {
                Object.defineProperty(HTMLVideoElement.prototype, 'currentTime', {
                    get: hookCurrentTime
                })
                Object.defineProperty(HTMLAudioElement.prototype, 'currentTime', {
                    get: hookCurrentTime
                })
            } catch (err) {
                _log(err);
            }

        }

        function _start() {
            _init();
            _encoder.start();
            _capturing = true;
        }

        function _stop() {
            _capturing = false;
            _encoder.stop();
            _destroy();
        }

        function _call(fn, p) {
            _oldSetTimeout(fn, 0, p);
        }

        function _step() {
            //_oldRequestAnimationFrame( _process );
            _call(_process);
        }

        function _destroy() {
            _log('Capturer stop');
            window.setTimeout = _oldSetTimeout;
            window.setInterval = _oldSetInterval;
            window.clearTimeout = _oldClearTimeout;
            window.requestAnimationFrame = _oldRequestAnimationFrame;
            window.Date.prototype.getTime = _oldGetTime;
            window.Date.now = _oldNow;
            window.performance.now = _oldPerformanceNow;
        }

        function _updateTime() {
            var seconds = _frameCount / _settings.framerate;
            if ((_settings.frameLimit && _frameCount >= _settings.frameLimit) || (_settings.timeLimit && seconds >= _settings.timeLimit)) {
                _stop();
                _save();
            }
            var d = new Date(null);
            d.setSeconds(seconds);
            if (_settings.motionBlurFrames > 2) {
                _timeDisplay.textContent = 'CCapture ' + _settings.format + ' | ' + _frameCount + ' frames (' + _intermediateFrameCount + ' inter) | ' + d.toISOString().substr(11, 8);
            } else {
                _timeDisplay.textContent = 'CCapture ' + _settings.format + ' | ' + _frameCount + ' frames | ' + d.toISOString().substr(11, 8);
            }
        }

        function _checkFrame(canvas) {

            if (canvasMotionBlur.width !== canvas.width || canvasMotionBlur.height !== canvas.height) {
                canvasMotionBlur.width = canvas.width;
                canvasMotionBlur.height = canvas.height;
                bufferMotionBlur = new Uint16Array(canvasMotionBlur.height * canvasMotionBlur.width * 4);
                ctxMotionBlur.fillStyle = '#0'
                ctxMotionBlur.fillRect(0, 0, canvasMotionBlur.width, canvasMotionBlur.height);
            }

        }

        function _blendFrame(canvas) {

            //_log( 'Intermediate Frame: ' + _intermediateFrameCount );

            ctxMotionBlur.drawImage(canvas, 0, 0);
            imageData = ctxMotionBlur.getImageData(0, 0, canvasMotionBlur.width, canvasMotionBlur.height);
            for (var j = 0; j < bufferMotionBlur.length; j += 4) {
                bufferMotionBlur[j] += imageData.data[j];
                bufferMotionBlur[j + 1] += imageData.data[j + 1];
                bufferMotionBlur[j + 2] += imageData.data[j + 2];
            }
            _intermediateFrameCount++;

        }

        function _saveFrame() {

            var data = imageData.data;
            for (var j = 0; j < bufferMotionBlur.length; j += 4) {
                data[j] = bufferMotionBlur[j] * 2 / _settings.motionBlurFrames;
                data[j + 1] = bufferMotionBlur[j + 1] * 2 / _settings.motionBlurFrames;
                data[j + 2] = bufferMotionBlur[j + 2] * 2 / _settings.motionBlurFrames;
            }
            ctxMotionBlur.putImageData(imageData, 0, 0);
            _encoder.add(canvasMotionBlur);
            _frameCount++;
            _intermediateFrameCount = 0;
            _log('Full MB Frame! ' + _frameCount + ' ' + _time);
            for (var j = 0; j < bufferMotionBlur.length; j += 4) {
                bufferMotionBlur[j] = 0;
                bufferMotionBlur[j + 1] = 0;
                bufferMotionBlur[j + 2] = 0;
            }
            gc();

        }

        function _capture(canvas) {

            if (_capturing) {

                if (_settings.motionBlurFrames > 2) {

                    _checkFrame(canvas);
                    _blendFrame(canvas);

                    if (_intermediateFrameCount >= .5 * _settings.motionBlurFrames) {
                        _saveFrame();
                    } else {
                        _step();
                    }

                } else {
                    _encoder.add(canvas);
                    _frameCount++;
                    _log('Full Frame! ' + _frameCount);
                }

            }

        }

        function _process() {

            var step = 1000 / _settings.framerate;
            var dt = (_frameCount + _intermediateFrameCount / _settings.motionBlurFrames) * step;

            _time = _startTime + dt;
            _performanceTime = _performanceStartTime + dt;

            media.forEach(function(v) {
                v._hookedTime = dt / 1000;
            });

            _updateTime();
            _log('Frame: ' + _frameCount + ' ' + _intermediateFrameCount);

            for (var j = 0; j < _timeouts.length; j++) {
                if (_time >= _timeouts[j].triggerTime) {
                    _call(_timeouts[j].callback)
                        //console.log( 'timeout!' );
                    _timeouts.splice(j, 1);
                    continue;
                }
            }

            for (var j = 0; j < _intervals.length; j++) {
                if (_time >= _intervals[j].triggerTime) {
                    _call(_intervals[j].callback);
                    _intervals[j].triggerTime += _intervals[j].time;
                    //console.log( 'interval!' );
                    continue;
                }
            }

            _requestAnimationFrameCallbacks.forEach(function(cb) {
                _call(cb, _time - g_startTime);
            });
            _requestAnimationFrameCallbacks = [];

        }

        function _save(callback) {

            if (!callback) {
                callback = function(blob) {
                    download(blob, _encoder.filename + _encoder.extension, _encoder.mimeType);
                    return false;
                }
            }
            _encoder.save(callback);

        }

        function _log(message) {
            if (_verbose) console.log(message);
        }

        function _on(event, handler) {

            _handlers[event] = handler;

        }

        function _emit(event) {

            var handler = _handlers[event];
            if (handler) {

                handler.apply(null, Array.prototype.slice.call(arguments, 1));

            }

        }

        function _progress(progress) {

            _emit('progress', progress);

        }

        return {
            start: _start,
            capture: _capture,
            stop: _stop,
            save: _save,
            on: _on
        }
    }

    (freeWindow || freeSelf || {}).CCapture = CCapture;

    // Some AMD build optimizers like r.js check for condition patterns like the following:
    if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
        // Define as an anonymous module so, through path mapping, it can be
        // referenced as the "underscore" module.
        define(function() {
            return CCapture;
        });
    }
    // Check for `exports` after `define` in case a build optimizer adds an `exports` object.
    else if (freeExports && freeModule) {
        // Export for Node.js.
        if (moduleExports) {
            (freeModule.exports = CCapture).CCapture = CCapture;
        }
        // Export for CommonJS support.
        freeExports.CCapture = CCapture;
    } else {
        // Export to the global object.
        root.CCapture = CCapture;
    }

}());